package persistence;

import Common.Comunication;
import Common.Detail;
import Common.EmailDetail;
import Common.EmailStatusDetail;
import Common.EmailStatusType;
import java.beans.XMLDecoder;
import java.beans.XMLEncoder;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;

public class DataManager<T> {

    private String fileName;
    private ArrayList<T> table;
    private boolean autoCommit;

    public boolean isAutoCommit() {
        return autoCommit;
    }

    public void setAutoCommit(boolean autoCommit) {
        this.autoCommit = autoCommit;
    }

    public DataManager() throws IOException, ClassNotFoundException {
        this("FileManager.xml", true);
    }
    
    public DataManager(String fileName) throws IOException, ClassNotFoundException {
        this(fileName, true);
    }
    
    public DataManager(String fileName, boolean autoCommit) throws IOException, ClassNotFoundException {
        this.fileName = fileName;
        this.autoCommit = autoCommit;
        table = new ArrayList<T>();
        if (!((new File(fileName)).exists())) {
            commit();
            return;
        }
        load();
    }

    /**
     * Load the table from disk
     * @throws IOException 
     */
    public final void load() throws IOException, ClassNotFoundException {
        FileInputStream in = null;
        try {
            in = new FileInputStream(fileName);
            // Esta clase carga objects almacenados en el archivo del fis
            XMLDecoder dec = new XMLDecoder(in);
//            ObjectInputStream dec = new ObjectInputStream(in);
            // Lee el object y lo retorna
            table = (ArrayList<T>)dec.readObject();
        } catch (java.lang.ArrayIndexOutOfBoundsException ex) {
        } finally {
            if (in != null) {
                in.close();
            }
        }
    }

    /**
     * Saves the items from the table on disk
     * @throws IOException 
     */
    public final void commit() throws IOException {
        FileOutputStream out = null;
        try {
            // Esta clase carga un archivo
            out = new FileOutputStream(fileName);
            // Esta clase carga objects almacenados en el archivo del fis
            XMLEncoder enc = new XMLEncoder(out);
//            ObjectOutputStream enc = new ObjectOutputStream(out);
            // Lee el object y lo retorna
            enc.writeObject(table);
            enc.close();
        } finally {
            if (out != null) {
                out.close();
            }
        }
    }
    
    /**
     * Load de previus state of table from disk
     * @throws IOException 
     */
    public void rollback() throws IOException, ClassNotFoundException {
        load();
    }
    
    /**
     * Retrieve the item which take the id
     * @param object object with id
     * @return complete item from table with the specified id in te object
     * @throws IOException 
     */
    public T find(T object) throws IOException {
        int index = table.indexOf(object);
        if (index < 0) {
            return null;
        }
        return table.get(index);
    }

    /**
     * Retrieve all items from table
     * @return All items
     */
    public List<T> findAll() {
        return table;
    }
    
    /**
     * Retrieve all items that matches with de object example specified
     * @param object example to verify the matches
     * @return Items which matches with specified object
     * @throws IOException
     * @throws IllegalArgumentException
     * @throws IllegalAccessException 
     */
    public List<T> findAllByExample(T object) throws IOException, IllegalArgumentException, IllegalAccessException {
        List<T> result = new ArrayList<T>();
        for (T item : table) {
            if (matchExample(object, item)) {
                result.add(item);
            }
        }
        return result;
    }
    
    /**
     * Verify if the values (not null toString) in the target object contains 
     * the values from the source object
     * @param source object with take the values to verify
     * @param target object to verify
     * @return true if matches false in other cases
     * @throws IllegalArgumentException
     * @throws IllegalAccessException 
     */
    public boolean matchExample(T source, T target) throws IllegalArgumentException, IllegalAccessException {
        Class sourceClass = source.getClass();
        Field[] declaredFields = sourceClass.getDeclaredFields();
        for (Field field : declaredFields) {
            field.setAccessible(true);
            Object valueSource = field.get(source);
            Object valueTarget = field.get(target);
            if (valueSource != null && valueTarget != null) {
                String stringSource = ("" + valueSource).toLowerCase();
                String stringTarget = ("" + valueTarget).toLowerCase();
                if (!stringTarget.contains(stringSource))
                return false;
            }
        }
        return true;
    }

    /**
     * Saves the changes for the specified object in the table. 
     * If does not exists the item them create
     * 
     * @param object item to save
     * @throws IOException 
     */
    public void save(T object) throws IOException {
        if (table.contains(object)) {
            table.set(table.indexOf(object), object);
            if (autoCommit) {
                commit();
            }
            return;
        }
        table.add(object);
        if (autoCommit) {
            commit();
        }
    }

    /**
     * Removes the specified object
     * @param object item to remove
     * @throws IOException 
     */
    public void remove(T object) throws IOException {
        if (table.contains(object)) {
            table.remove(object);
            if (autoCommit) {
                commit();
            }
        }
    }

    /**
     * Truncates the table
     * @throws IOException 
     */
    public void truncate() throws IOException {
        table.clear();
        if (autoCommit) {
            commit();
        }
    }
    
    public static void main(String[] args) {
        try {
            DataManager<Common.Comunication> comdm = new DataManager<Common.Comunication>("test_com.xml");
            Common.Comunication comunication = new Common.Comunication("senter 1", "receiver 1");
            comdm.save(comunication);
            System.out.println(comdm.find(comunication));
            System.out.println("**************************************************");
            DataManager<Common.EmailDetail> emddm = new DataManager<Common.EmailDetail>("test_emd.xml");
            Common.EmailDetail emailDetail = new Common.EmailDetail("senter 1", "receiver 1");
            emddm.save(emailDetail);
            System.out.println(emddm.find(emailDetail));
            System.out.println("**************************************************");
            DataManager<Common.EmailStatusDetail> emsddm = new DataManager<Common.EmailStatusDetail>("test_emsd.xml");
            Common.EmailStatusDetail emailStatusDetail = new Common.EmailStatusDetail(15l, EmailStatusType.NEW);
            emsddm.save(emailStatusDetail);
            System.out.println(emsddm.find(emailStatusDetail));
            System.out.println("**************************************************");
            DataManager<Common.Message> msgdm = new DataManager<Common.Message>("test_msg.xml");
            Common.Message message = new Common.Message(new Comunication("senter1", "receiver1"), new Detail(new EmailDetail("subject1", "body1"), new EmailStatusDetail(15l, EmailStatusType.NEW)));
            msgdm.save(message);
            System.out.println(msgdm.find(message));
            System.out.println("**************************************************");
        } catch (ClassNotFoundException ex) {
            Logger.getLogger(DataManager.class.getName()).log(Level.SEVERE, null, ex);
        } catch (IOException ex) {
            Logger.getLogger(DataManager.class.getName()).log(Level.SEVERE, null, ex);
        }
    }
    
    public Object returnForcedObject() {
        return table;
    }

}
